技巧52 镜像的扁平化
Dockerfile的设计以及它们产出Docker镜像的结果便是,最终镜像里包含了Dockerfile里每一步的数据状态。在构建镜像的过程中,可能需要复制私密信息来确保构建工作可以顺利进行。这些所谓的私密信息可能是SSH密钥、证书或密码文件等。在提交镜像前删除这些私密信息可能不会提供任何实质性的保护,因为它们将出现在最终镜像的更高的层里,而恶意用户则可以轻松地从镜像中提取它们。
解决这一问题的其中一个办法便是将得到的镜像扁平化。
问题
想要从镜像的层历史中移除私密信息。
解决方案
基于该镜像创建一个容器,将它导出再导入,然后给它打上最初镜像ID的标签。
为了演示这种做法的可用场景,让我们在一个新目录里创建一个简单的Dockerfile,该目录下藏着一个大秘密。执行 mkdir secrets && cdsecrets
,然后在该目录里创建一个包含代码清单7-3所示的内容的Dockerfile。
代码清单7-3 一个复制然后删除私密信息的Dockerfile
FROM debian
RUN echo "My Big Secret" >> /tmp/secret_key ⇽--- 在构建里放置一个带有一些私密信息的文件
RUN cat /tmp/secret_key ⇽--- 对该私密文件做一些事情。当前这个Dockerfile只会列出该文件的内容,但是你的Dockerfile可能会SSH到其他服务器或者在镜像里加密该私密信息
RUN rm /tmp/secret_key ⇽--- 删除该私密文件
现在执行 docker build -t mysecret .
以构建该Dockerfile并给它打标签。
一旦它完成构建,可以通过 docker history
命令检查得到的Docker镜像的层:
$ docker history mysecret ⇽--- 使用刚创建的镜像的名称执行docker history命令
IMAGE CREATED CREATED BY SIZE
55f3c131a35d 3 days ago /bin/sh -c rm /tmp/secret_key ⇽--- 这一层是删除密钥的地方
5b376ff3d7cd 3 days ago /bin/sh -c cat /tmp/secret_key 0 B
5e39caf7560f 3 days ago /bin/sh -c echo "My Big Secret" >> /tmp/se 14 B ⇽--- 这一层是添加密钥的地方
c90d655b99b2 6 days ago /bin/sh -c #(nop) CMD [/bin/bash] 0 B
30d39e59ffe2 6 days ago /bin/sh -c #(nop) ADD file:3f1a40df75bc567 85.01 MB ⇽--- 在这一层添加了Debian文件系统。注意,该分层是历史记录里最大的一个
511136ea3c5a 20 months ago 0 B ⇽--- 最开始的(空的)层
现在试想一下用户从一个公开的注册中心下载了这一镜像。你可以检索镜像层的历史然后执行如下命令列出私密信息:
$ docker run 5b376ff3d7cd cat /tmp/secret_key
My Big Secret
这里我们运行了一个特定的层然后将它构造出来, cat
我们在更高的层里已经删除的那个密钥的内容。正如所见,文件是可以访问的。
至此,用户有一个里面藏有秘密的“危险”容器,我们已经见证了的确可以“黑”到里面的私密信息。要让这个镜像变得安全,需要将该镜像 扁平化 处理。这意味着用户可以在该镜像里保留相同的数据但是会删除中间层的信息。为了达成这一目的,需要将该镜像导出为一个简单运行的容器然后再重新导入并给得到的镜像打上标签:
$ docker run -d mysecret /bin/true ⇽--- 执行一个简单的命令让容器可以快速退出,因为并不需要它处于运行状态
28cde380f0195b24b33e19e132e81a4f58d2f055a42fa8406e755b2ef283630f
$ docker export 28cde380f | docker import - mysecret ⇽--- 执行docker export,把容器ID当作参数并输出文件系统的内容的一个TAR文件。这一操作被管道到docker import,它会以TAR文件的内容作为输入,基于这些内容创建一个镜像
$ docker history mysecret
IMAGE CREATED CREATED BY SIZE
fdbeae08751b 13 seconds ago 85.01 MB ⇽--- docker history的输出现在只显示最后那一组文件的层
传给 docker import
命令的 -
参数指明用户想要从命令的标准输入读取TAR文件内容。 docker import
的最后一个参数指明该如何给导入的镜像打标签。在这个例子里它会覆盖之前的标签。
由于现在镜像里只有一个层,因此就没有藏着秘密的层记录。现在再也无法从镜像提取任何秘密了。
讨论
本技巧非常实用,它可以被应用在本书的很多例子上,如7.3节中的例子。
打算应用本技巧时,需要考虑到的一点是,使用本技巧会失去镜像层在层缓存和下载时间方面的优势。如果你所在的组织已经深思熟虑,计划好应用这一问题,那么本技巧在这些镜像的实际使用时会是相当有用的。